package jwt

import (
	"myRbac/models"
	"time"

	"github.com/golang-jwt/jwt/v4"
)

// 在token中保存id、username
type UserInfo struct {
	Id       int
	UserName string
	Roles    []models.UserRole
}

type MyClaims struct {
	User UserInfo
	// 标准Claims结构体，可传入配置项
	jwt.RegisteredClaims
}

// 错误集合
const (
	ISS   = "签发者异常"
	EXP   = "过期时间异常"
	AUD   = "接受者异常"
	IAT   = "签发时间异常"
	NBF   = "该令牌尚未生效"
	JTI   = "身份标识校验错误"
	SUB   = "用户身份错误"
	OTHER = "身份校验失败"
	OK    = "身份校验成功"
)

// 过期时间
const TokenExpireDuration = time.Hour * 24

// const TokenExpireDuration = time.Minute

// 签名
var mySecret = []byte("shuonihao")

// 生成token，传入UserInfo结构体
func GenerateToken(userInfo UserInfo) (string, error) {
	expirationTime := time.Now().Add(TokenExpireDuration) // 两个小时有效期
	claims := &MyClaims{
		User: userInfo,
		RegisteredClaims: jwt.RegisteredClaims{
			// 过期时间
			ExpiresAt: jwt.NewNumericDate(expirationTime),
			// 签发时间
			IssuedAt: jwt.NewNumericDate(time.Now()),
			// 生效时间
			NotBefore: jwt.NewNumericDate(time.Now()),
			// 签发者
			Issuer: "dengwenhao",
			// 主题
			Subject: "测试",
			// 用户类型
			Audience: []string{"ok", "no"},
		},
	}
	// 生成Token，指定签名算法和claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 签名
	if tokenString, err := token.SignedString(mySecret); err != nil {
		return "", err
	} else {
		return tokenString, nil
	}
}

// 获取签名，需要传入解析结构的是一个函数
func Secret() jwt.Keyfunc {
	return func(token *jwt.Token) (interface{}, error) {
		return mySecret, nil
	}
}

// 解析
func ParseToken(tokenss string) (*MyClaims, string) {
	token, err := jwt.ParseWithClaims(tokenss, &MyClaims{}, Secret())
	if err != nil {
		if ve, ok := err.(*jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, OTHER
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				return token.Claims.(*MyClaims), EXP
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, NBF
			} else {
				return nil, JTI
			}
		}
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
		return claims, OK
	}
	return nil, OTHER
}

// 续签token
func RenewToken(claims *MyClaims) (string, error) {
	// 若token过期不超过10分钟则给它续签
	if withinLimit(claims.ExpiresAt.Unix(), 600) {
		// 可以续签
		return GenerateToken(claims.User)
	}
	return "", nil
}

// 计算过期时间是否超过定额
func withinLimit(s int64, l int64) bool {
	e := time.Now().Unix()
	return e-s < l
}
